// RAMbots
// CS425 2009 Game Programming I Class
// Basic Main Template
// Jan Allbeck

#include "DarkGDK.h"
#include "TexturedObj.h"
#include "Setup.h"
#include "Objects3D.h"
#include "User.h"
#include "NonUser.h"
#include "CreateEnv.h"
#include "Utilities.h"
#include "PathTraverser.h"
#include <iostream>
#include <list>
#include "Pathfinder.h"
#include "Camera.h"
#include "Robot.h"
#include "GameTime.h"
#include "Button.h"
#include "RadioButton.h"
#include "Hostile.h"
#include "Hostage.h"

using namespace std;

// create list for holding non user controlled 3D objects in world
NonUser world3D;

// create list for holding user controlled 3D objects in world
User user;

// vector to hold robots and hostiles
vector<Robot *> myRobots;
vector<Hostile *> myHostiles;
vector<Hostage *> myHostages;
Agent * commander;
Agent * activeAgent;

// GUI elements
Camera * mainCamera;
list<Camera *> cameras;
list<RadioButton *> pcAgentButtons;
//Button minimap;

// game timer for testing robot animation.
GameTime myGameTime;

// vector<Triple> wayPointPositions = vector<Triple>( 21, ' ' );

// Path for testing purposes
PathTraverser walkPaths;
list<Triple> testPath;
extern Grid<char> occupancy;
Pathfinder goGetEm;   // holds reference to occupancy grid, given point A and B.
Triple playerPos;

enum GameMode
{
	Start,
	Run,
	Pause,
	Lost,
	Won,
	Quit
};

int navigationTime = 0;
int constructTime = 0;
int smoothTime = 0;

bool gamePaused = false;
bool isRobotActive = false;		// boolean to determine whether or not a robot is active.
bool isMiniMapOn = true;		// boolean to determine whether or not to display minimap.
bool isHUDOn = false;			// boolean to toggle HUD
bool isHostagesFree = false;	// boolean to check if all hostages are free.
bool isRobotsDisabled = false;	// boolean to check if all robots are disabled.
bool isHostilesDead = false;	// boolean to check if all hostiles are dead.

// We should create camera class
void updateCamera();			// function to move the camera in 1st person
void updateTopDownCamera();		// function to move an overhead camera
void updateTrackerCamera();		// follows user overhead
void scanAndUpdateRobots();		// update list of robots.
void scanAndUpdateHostiles();	// update list of hostiles.
void displaySmallCameras();		// update list of cameras.
void displayMainCamera();		// view camera of currently selected agent
void printHUD(int start, int elapsed);				// print heads up display
void displayButtons();			// show all necessary GUI buttons
void selectAgent();				// change control to picked robot / commander
void parseRobot(Agent* roboto);	// Testing/Debug function. Will be used to issue commands to robots.
void printMiniMap();
int checkGameState();			// check game state.

// for button testing
void clickHandler() { dbPrint( "Yay!" ); }

void DarkGDK ( void )
{
	// set display mode
	Setup initialise(1024, 768, 32, 1);
	initialise.syncFrame(30, 1);

	dbColorBackdrop ( dbRGB ( 0, 0, 0 ) ); // Lecture 7: change background to black

	/////////////////////////////////////////////////////////////////////////////////
	// Show a loading message
	dbSetTextSize( 48 );
	dbCenterText( dbScreenWidth()/2, dbScreenHeight()/2 - 25, "Patience is a virtue..." );
	dbSync ( );		// Need to sync twice because of double buffering. See DarkGDK docs
	dbSync ( );
	/////////////////////////////////////////////////////////////////////////////////

	// Restore normal text size
	dbSetTextSize( 12 );

	myGameTime = GameTime();
	myGameTime.initialize();

	dbLoadImage( "Models\\GUI-mockup.png", 20000 );

	// BE SURE NOT TO USE ALREADY TAKEN IMAGE IDS!
	Button doStuff = Button::Button( "Models\\Generic-Button.png", 20, "Howdy", 600, 300, & clickHandler );
	doStuff.setVisible( true );

	/*
	int loadTime = 0;
	myGameTime.startTimer();
	dbLoadBitmap( "Models\\Generic-Button.png", 5 );

	while( ! dbBitmapExist( 5 ) )
	dbSleep(1);

	loadTime = myGameTime.stopTimer(); 

	int imgLoadTime = 0;
	myGameTime.startTimer();
	dbLoadImage( "Models\\Generic-Button.png", 5 );

	while( ! dbImageExist( 5 ) )
	dbSleep(1);

	imgLoadTime = myGameTime.stopTimer();

	int spriteLoadTime = 0;
	myGameTime.startTimer();
	dbSetImageColorKey( 255, 255, 255 );
	dbCreateAnimatedSprite( 8, "Models\\Generic-Button.png", 3, 1, 8 );

	while( ! dbSpriteExist( 8 ) )
	dbSleep(1);

	spriteLoadTime = myGameTime.stopTimer();

	//dbSizeSprite(8, 50, 50 );
	dbShowSprite( 8 );
	*/

	//myRobots = vector<Robot *>();

	//cameras = vector<Camera *>();

	// load the environment
	Environment *env = new Environment();
	env->useDefaultEnv();  // just use the hard coded one for now
	dbSetCurrentBitmap(0);

	// Turn off camera repositioning whenever a new object is created
	dbAutoCamOff();

	// for use with the 1st person camera
	dbPositionCamera			( 0 , 200.0f , 40.0f , 100.0f );
	//dbAutomaticCameraCollision	( 0 , 10 , 0 );
	dbSetCameraRange			( 0 , 1 , 2000 );

	/*
	float mx,my,mz = 0;
	float rx,ry,rz = 0;

	Camera *miniMap = new Camera(93939, mx,my,mz,rx,ry,rz);
	*/

	// for use with top down camera
	dbPositionCamera(0 , 1000.0f , 1000.0f, 1000.0f );  // top down camera
	dbXRotateCamera(0, 90.0f);

	//env->setupVis();  // what are the different segments of the environment 
	//env->showVis();  // show the areas in different colors


	// testing counters
	int startTime = 0;
	int elapsedTime = 0;

	// define robots.
	Robot * wally = myRobots[0];
	Robot * marvin = myRobots[1];
	Robot * johnny5 = myRobots[2];

	// this will be the 'active robot'
	Robot * activeRobot;

	// main game loop
	while ( LoopGDK ( ) )
	{
		if ( dbEscapeKey ( ) )  // end the game
			return;

		if( dbKeyState( 25 ) ) // p is pressed
		{
			gamePaused = true;

			while( dbKeyState( 25 ) ) { dbSync(); }
			while( !dbKeyState( 25 ) ) { dbSync(); }
			gamePaused = false;
		}

		//displayMainCamera();
		displayButtons();

		if ( dbKeyState( 19 ) )	// r is pressed
		{
			// reset game
		}

		// toggle minimap on/off
		if ( dbKeyState( 50 ) == 1)	// m is pressed
		{
			if (isMiniMapOn)
			{
				isMiniMapOn = false;
			}
			else
			{
				isMiniMapOn = true;
			}
		}
		// toggle heads up display
		if ( dbKeyState( 20 ) == 1)	// t is pressed
		{
			if (isHUDOn)
			{
				isHUDOn = false;
			}
			else
			{
				isHUDOn = true;
			}
		}

		////////////////////////////////////////////////////////////////////////////
		// check robot selection keys.
		if ( dbKeyState( 2 ) == 1)
		{
			if (!isRobotActive)
			{
				// robot 1 is selected
				activeRobot = wally;
				isRobotActive = true;
			}
			else
			{
				isRobotActive = false;
			}
		}
		if ( dbKeyState( 3 ) == 1)
		{
			if (!isRobotActive)
			{
				// robot 1 is selected
				activeRobot = marvin;
				isRobotActive = true;
			}
			else
			{
				isRobotActive = false;
			}
		}
		if ( dbKeyState( 4 ) == 1)
		{
			if (!isRobotActive)
			{
				// robot 1 is selected
				activeRobot = johnny5;
				isRobotActive = true;
			}
			else
			{
				isRobotActive = false;
			}
		}

		////////////////////////////////////////////////////////////////////////////
		// check boolean flags for game toggles.
		if (isHUDOn)
		{
			printHUD(startTime, elapsedTime);
		}

		if (isMiniMapOn)
		{
			printMiniMap();
		}

		// Draw button
		//doStuff.display();
		// testing occupancy grid check.


		// the double "check" ensures that the number "2" was pressed, and the timer has not expired.
		/*
		if ( (elapsedTime < 5000) && (startTime > 0) ) 
		{
		dbPrintC("Playing animation " );
		// create game time object, and then start timer, then stop timer will return the elapsed time.
		wally->playAnimation(Animation::Die);
		elapsedTime = myGameTime.getTime() - startTime;
		}
		*/

		//dbCopyBitmap( 5, 0 );

		//dbPasteSprite( 20, 0, 600 );
		//dbPasteImage(20, 0, 500, 1 );
		//dbCopyBitmap( TEMP_BITMAP, 0 );

		

		// test robot camera image
		//dbPasteImage( 26, 116, 592 );

		// DRAW MAIN MINIMAP HERE!!
		updateCamera();      // update the 1st person camera

		user.scanAndUpdate();  // update the player's actions
		world3D.scanAndUpdate(); // update the NPC's 
		scanAndUpdateRobots(); // update the robots

		// parse robot commands for active robots.
		if (isRobotActive)
		{
			parseRobot(activeRobot);
		}

		// check game conditional states.
		int gameWinOrLose = checkGameState();

		// win game
		if (gameWinOrLose == 4)
		{
			// play a happy song.
			return;
		}

		// lose game
		if (gameWinOrLose == 9)
		{
			// play a sad song.
			return;
		}


		
		//updateTopDownCamera();  // update the overhead camera
		// updateTrackerCamera();
		env->updateDoors();		// Do we need to open or close the doors?
		//env->updateVis();		// what can the camera see
		showFPS();				// put frames per second on the screen



		// here we make a call to update the contents of the screen
		dbSync ( );
	}

	// should be cleaning up memory. Deleting what we created


	// and now everything is ready to return back to Windows
	return;
}


////////////////////////////////////////////////////////////////////////////
// First person camera
// Use mouse to point in a direction and arrow keys to move
void 
updateCamera()
{
	int oldX = ( ( ( int ) ( dbCameraPositionX ( 0 ) - 50 ) / 100 ) + 1 );
	int oldZ = ( ( ( int ) ( dbCameraPositionZ ( 0 ) - 50 ) / 100 ) + 1 );
	int newX;
	int newZ;
	float	PLAYERSPEED		= 4.5f;
	float CameraAngleY;
	float CameraAngleX;

	//dbControlCameraUsingArrowKeys ( 0 , 1 , 1 );
	dbPositionMouse ( dbScreenWidth()/2 , dbScreenHeight()/2 );

	if ( dbUpKey() || dbKeyState( 17 ) == 1 )
	{
		dbMoveCamera ( 0 , PLAYERSPEED );
	}
	if ( dbDownKey() || dbKeyState( 31 ) == 1 ) 
	{
		dbMoveCamera ( 0 , -PLAYERSPEED );
	}

	if ( dbLeftKey() || dbKeyState( 30 ) == 1 )
	{
		dbYRotateCamera ( 0 , dbCameraAngleY( 0 ) -90 );
		dbMoveCamera ( 0 , PLAYERSPEED );
		dbYRotateCamera ( 0 , dbCameraAngleY( 0 ) +90 );
	}

	if ( dbRightKey() || dbKeyState( 32 ) == 1 )
	{
		dbYRotateCamera ( 0 , dbCameraAngleY( 0 ) + 90 );
		dbMoveCamera ( 0 , PLAYERSPEED );
		dbYRotateCamera ( 0 , dbCameraAngleY( 0 ) -90 );
	}


	float cx = dbCameraPositionX ( 0 );
	float cy = dbCameraPositionY ( 0 ) -40;
	float cz = dbCameraPositionZ ( 0 );

	float OldCamAngleY = dbCameraAngleY ( 0 );
	float OldCamAngleX = dbCameraAngleX ( 0 );

	CameraAngleY = dbWrapValue ( ( dbCameraAngleY ( 0 ) +  dbMouseMoveX ( ) * 0.4f ) );
	CameraAngleX = dbWrapValue ( ( dbCameraAngleX ( 0 ) + dbMouseMoveY ( ) * 0.4f ) );

	if ( CameraAngleX > 80 && CameraAngleX <180 ) CameraAngleX = 80;
	if ( CameraAngleX < 270 && CameraAngleX > 180 ) CameraAngleX = 270;

	dbYRotateCamera	( 0 , dbCurveAngle ( CameraAngleY , OldCamAngleY , 6 ) );
	dbXRotateCamera	( 0 , dbCurveAngle ( CameraAngleX , OldCamAngleX , 6 ) );

	dbPositionCamera ( 0 , dbCameraPositionX ( 0 ) , 40 , dbCameraPositionZ ( 0 ) );
	dbSetPointLight	( 0 , dbCameraPositionX ( 0 ) , 80 , dbCameraPositionZ ( 0 ) );

	////GUN
	//  dbSetObjectToCameraOrientation ( 10 );
	//  dbSetObjectToCameraOrientation ( 11 );

	//  dbPositionObject		( 10 , dbCameraPositionX ( 0 ) , dbCameraPositionY ( 0 ) , dbCameraPositionZ( 0 ) );
	//  dbPositionObject		( 11 , dbCameraPositionX ( 0 ) , dbCameraPositionY ( 0 ) , dbCameraPositionZ( 0 ) );

	// draw cross hair
	dbPasteImage ( 12 , ( dbScreenWidth( ) / 2 ) - 16 , ( dbScreenHeight( ) / 2 ) - 16 , 1 );

}

/////////////////////////////////////////////////////////////////////////////
// Update the overhead camera
// Arrow keys move the camera left-right and up-down
// PgUp and PgDn zoom in and out
void updateTopDownCamera()
{
	const float CAM_SPEED = 20.0;

	if ( dbKeyState( 17 ) == 1 )  // Move up
	{
		dbPositionCamera ( 0 , dbCameraPositionX(0) , dbCameraPositionY(0) , dbCameraPositionZ(0) + CAM_SPEED );
	}
	if ( dbKeyState( 31 ) == 1 ) // Move down
	{
		dbPositionCamera ( 0 , dbCameraPositionX(0) , dbCameraPositionY(0) , dbCameraPositionZ(0) - CAM_SPEED );
	}

	if ( dbKeyState( 30 ) == 1 ) // Move left
	{
		dbPositionCamera ( 0 , dbCameraPositionX(0) - CAM_SPEED, dbCameraPositionY(0) , dbCameraPositionZ(0));
	}

	if ( dbKeyState( 32 ) == 1 ) // Move right
	{
		dbPositionCamera ( 0 , dbCameraPositionX(0) + CAM_SPEED, dbCameraPositionY(0) , dbCameraPositionZ(0));
	}

	if ( dbKeyState( 78 ) == 1 )   // Plus key = zoom in
		dbMoveCamera ( 0 , CAM_SPEED );

	if ( dbKeyState( 74 ) == 1 )  // Minus key = zoom out
		dbMoveCamera ( 0 , -CAM_SPEED );
}

////////////////////////////////////////////////////////////////////////////
// update tracker camera
void updateTrackerCamera()
{
	float x, y, z;
	x = playerPos.x;
	y = 500;
	z = playerPos.z;
	dbPositionCamera( 0, x, y, z );
	dbPointCamera(0, x, 0, z );
}

////////////////////////////////////////////////////////////////////////////
// pathfinding tester
Triple getRandPos()
{
	dbRandomize( dbTimer() );
	unsigned int x, y;
	do
	{
		x = dbRND( 21 );
		y = dbRND( 49 );
	}
	while( ! occupancy.isValid( x, y) );

	return goGetEm.gridToPos( Position( x, y ) );
}

////////////////////////////////////////////////////////////////////////////
// scan and update all robots.
void scanAndUpdateRobots()
{
	for (int i = 0; i < myRobots.size(); i++) {
		myRobots[i]->update();
	}

	// if all robots are gone, then set isRobotsDisabled = true.
	if (myRobots.size() == 0)
	{
		isRobotsDisabled = true;
	}
}

////////////////////////////////////////////////////////////////////////////
// scan and update all hostages
void scanAndUpdateHostages()
{
	for (int i = 0; i < myHostages.size(); i++) {
		myHostages[i]->update();
	}

	// if all hostiles are gone, then set isHostagesFree = true.
	if (myHostages.size() == 0)
	{
		isHostagesFree = true;
	}
}

////////////////////////////////////////////////////////////////////////////
// scan and update all hostiles
void scanAndUpdateHostiles()
{
	for (int i = 0; i < myHostiles.size(); i++) {
		myHostiles[i]->update();
	}

	// if all hostiles are gone, then set isHostilesDead = true.
	if (myHostiles.size() == 0)
	{
		isHostilesDead = true;
	}
}

void displaySmallCameras()
{
	list<Camera *>::iterator cam;

	for (cam = cameras.begin(); cam != cameras.end(); cam++) 
	{
		(*cam)->display();
	}
}

void displayMainCamera()
{
	list<Camera *>::iterator cam;

	for (cam = cameras.begin(); cam != cameras.end(); cam++) 
	{
		if( (*cam)->isActive() )
		{
			(*cam)->displayMain();
			break;
		}
	}
}

void selectAgent()
{
	dbPrint( "Agent Selected" );

	list<RadioButton *>::iterator button;

	for (button = pcAgentButtons.begin(); button != pcAgentButtons.end(); button++) 
	{
		if( (*button)->isPressed() )
		{
			activeAgent = ( button == pcAgentButtons.begin() ) ? commander : (*(myRobots.begin()));
			break;
		}
	}
}


void displayButtons()
{
	list<RadioButton *>::iterator button;

	for (button = pcAgentButtons.begin(); button != pcAgentButtons.end(); button++) 
	{
		(*button)->display();
	}
}


////////////////////////////////////////////////////////////////////////////
// print heads up display
void printHUD(int startTime, int elapsedTime) 
{
	dbSetCursor( 100, 100 );
	dbInk( dbRGB(255, 255, 255), dbRGB(255, 255, 255) );
	dbPrint( "Pathfinding Test" );
	dbPrintC( "Navigation time: " );
	dbPrintC( (double)navigationTime );
	dbPrint( " ms" );
	dbPrintC( "Construct time: " );
	dbPrintC( (double)constructTime );
	dbPrint( " ms" );
	dbPrintC( "Smooth time: " );
	dbPrintC( (double)smoothTime );
	dbPrint( " ms" );
	dbPrintC( "Key pressed: " );
	dbPrint( (double)dbScanCode() );
	dbPrintC("X coords: " );
	dbPrint( (double) dbMouseX() );
	dbPrintC("Y coords: " );
	dbPrint( (double) dbMouseY() );
	dbPrintC("Start time: " );
	dbPrint( (double) startTime );
	dbPrintC("Elapsed time: " );
	dbPrint( (double) elapsedTime );
	dbPrintC("Robot cam rotation y: " );
	dbPrint( (double) dbCameraAngleY((cameras.front()++)->getID()) );
	dbPrintC( "Robot y rotation: " );
	dbPrint( (double) myRobots[0]->getRotY() );
	/*
	dbPrintC("Bitmap width: " );
	dbPrint( (double) dbBitmapWidth( TEMP_BITMAP ) );
	dbPrintC("Bitmap load time: ");
	dbPrintC( (double) loadTime );
	dbPrint( " ms" );
	dbPrintC("Image load time: ");
	dbPrintC( (double) imgLoadTime );
	dbPrint( " ms" );
	dbPrintC("Sprite load time: ");
	dbPrintC( (double) spriteLoadTime );
	dbPrint( " ms" );
	*/
}


////////////////////////////////////////////////////////////////////////////
// display the minimap
void printMiniMap()
{
	// draw minimap
	int miniExists = dbBitmapExist( 10 );
	int width = dbBitmapWidth( 10 );
	int height = dbBitmapHeight( 10 );
	//dbCopyBitmap( 10, 0, 0, 200, 480, 0, 0, 256, 340, 766 );
	dbCopyBitmap( 10, 0 );
	// show gui
	dbPasteImage( 20000, 0, dbScreenHeight() - 192, 1 );
}

////////////////////////////////////////////////////////////////////////////
// check state of game.
int checkGameState()
{
	int gameState = 0;

	if ( (isHostagesFree == true) || (isHostilesDead == true) )
	{
		// all hostages are free!  or... 
		// all hostiles are dead!
		gameState = 9; // win condition.
	}

	if ( (isRobotsDisabled == true) && (isHostagesFree == false) )
	{
		// all robots are disabled, and there are still hostages.
		gameState = 4; // lose condition.
	}

	return gameState;
}

////////////////////////////////////////////////////////////////////////////
// parse robot actions
void parseRobot(Agent* someRobot)
{
	int distance = 5;						// distance to move robot by.
	Triple p = someRobot->getPosition();	// get current robot position

	// parse arrow keys.
	if ( dbKeyState(200) == 1 )				// up arrow pressed.
	{
		float direction = someRobot->getRotY();
		float newX = dbNewXValue( p.x, direction, distance );
		float newZ = dbNewZValue( p.z, direction, distance );
		Triple forward = Triple( newX, p.y, newZ );
		someRobot->moveTo(forward);
	}
	if ( dbKeyState(208) == 1 )				// down arrow pressed.
	{
		float direction = dbWrapValue(180 + someRobot->getRotY());
		float newX = dbNewXValue( p.x, direction, distance );
		float newZ = dbNewZValue( p.z, direction, distance );
		Triple backward = Triple( newX, p.y, newZ );
		someRobot->moveTo(backward);
	}
	if ( dbKeyState(203) == 1 )				// left arrow pressed.
	{
		float direction = dbWrapValue(someRobot->getRotY() - 90 );
		float newX = dbNewXValue( p.x, direction, distance );
		float newZ = dbNewZValue( p.z, direction, distance );
		Triple left = Triple( newX, p.y, newZ );
		someRobot->moveTo(left);
	}

	if ( dbKeyState(205) == 1 )				// right arrow pressed.
	{
		float direction = dbWrapValue(someRobot->getRotY() + 90);
		float newX = dbNewXValue( p.x, direction, distance );
		float newZ = dbNewZValue( p.z, direction, distance );
		Triple right = Triple( newX, p.y, newZ );
		someRobot->moveTo(right);
	}
	if ( dbKeyState( 13 ) )	// = is pressed
	{
		// robot disable baddie
		// TODO: get baddie ID and attack
	}

	if ( dbKeyState( 10 ) )	// 9 is pressed
	{
		// robot self destruct
		someRobot->playAnimation(Animation::Die);
		// TODO: set robot to flipped
	}
}
